feat(miniapp): 实现小程序加密网络通道服务端支持,修复 HMAC 签名与错误处理 Bug#3969
feat(miniapp): 实现小程序加密网络通道服务端支持,修复 HMAC 签名与错误处理 Bug#3969
Conversation
Agent-Logs-Url: https://github.com/binarywang/WxJava/sessions/f3aba758-8b4a-479f-96bd-88ce00a9c176 Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
Agent-Logs-Url: https://github.com/binarywang/WxJava/sessions/f3aba758-8b4a-479f-96bd-88ce00a9c176 Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
🤖 Augment PR SummarySummary: This PR improves MiniApp “加密网络通道/getUserEncryptKey” server-side support by fixing signature calculation and strengthening error handling. Changes:
🤖 Was this summary useful? React with 👍 or 👎 |
| new SecretKeySpec(keyBytes, "AES"), | ||
| new IvParameterSpec(ivBytes)); | ||
| return new String(cipher.doFinal(dataBytes), UTF_8); | ||
| } catch (Exception e) { |
There was a problem hiding this comment.
decryptWithEncryptKey catches all Exception and wraps it into WxRuntimeException, which means invalid hexIv (from hexToBytes’s IllegalArgumentException) won’t be observable as IllegalArgumentException by callers and the specific validation message is lost; is that intended given the Javadoc contract on hexToBytes? Other locations where this applies: weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java:141.
Severity: medium
Other Locations
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java:141
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| public void testEncryptAndDecryptWithEncryptKey() { | ||
| String plainText = "{\"userId\":\"12345\",\"amount\":100}"; | ||
|
|
||
| String encrypted = WxMaCryptUtils.encryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, plainText); |
There was a problem hiding this comment.
These tests only assert encrypt/decrypt round-trip symmetry, so they can still pass even if the algorithm/encoding doesn’t match WeChat’s expected wire format (both sides could be consistently wrong). Consider adding at least one fixed test vector (known plaintext → expected ciphertext, or vice versa) from the official doc/examples to validate interoperability.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ee161b56f1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| public static String decryptWithEncryptKey(String encryptKey, String hexIv, String encryptedData) { | ||
| try { | ||
| byte[] keyBytes = Base64.decodeBase64(encryptKey); | ||
| byte[] ivBytes = hexToBytes(hexIv); |
There was a problem hiding this comment.
Handle getUserEncryptKey iv as 16-byte IV
decryptWithEncryptKey unconditionally hex-decodes iv (hexToBytes), but the project’s own getUserEncryptKey response examples use 16-character values like 6003f73ec441c386 (WxMaInternetResponse/WxMaInternetUserKeyInfo), which become only 8 bytes after hex decoding; AES-CBC requires a 16-byte IV, so this path will throw at runtime for documented API-shaped inputs and make the new encrypt/decrypt helpers unusable in that scenario.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
该 PR 面向 weixin-java-miniapp 模块补齐/修复小程序加密网络通道(getUserEncryptKey)服务端支持:修正 HMAC 签名 key 的取值方式、完善错误码判断,并新增基于 encrypt_key + iv 的 AES-CBC 加解密工具,配套新增单测验证基础对称性。
Changes:
- 修复
WxMaInternetServiceImpl的 HMAC-SHA256 key 取值(Base64 解码为原始密钥字节)与错误码判断(非 0 且非 null 才视为错误)。 - 在
WxMaCryptUtils新增加密网络通道专用的 AES-128-CBC 加/解密方法与 Hex iv 转换辅助方法。 - 为新增加/解密方法补充对称性单测。
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaInternetServiceImpl.java | 修正签名计算的 HMAC key 字节来源,并加强 errcode 判错逻辑 |
| weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java | 新增加密网络通道 AES-CBC 加/解密能力与 Hex iv 转字节工具 |
| weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtilsTest.java | 增加加密网络通道加/解密对称性测试 |
| public static String decryptWithEncryptKey(String encryptKey, String hexIv, String encryptedData) { | ||
| try { | ||
| byte[] keyBytes = Base64.decodeBase64(encryptKey); | ||
| byte[] ivBytes = hexToBytes(hexIv); | ||
| byte[] dataBytes = Base64.decodeBase64(encryptedData); | ||
|
|
||
| Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); | ||
| cipher.init(Cipher.DECRYPT_MODE, | ||
| new SecretKeySpec(keyBytes, "AES"), | ||
| new IvParameterSpec(ivBytes)); | ||
| return new String(cipher.doFinal(dataBytes), UTF_8); | ||
| } catch (Exception e) { | ||
| throw new WxRuntimeException("AES解密失败!", e); | ||
| } |
| public static String encryptWithEncryptKey(String encryptKey, String hexIv, String data) { | ||
| try { | ||
| byte[] keyBytes = Base64.decodeBase64(encryptKey); | ||
| byte[] ivBytes = hexToBytes(hexIv); | ||
|
|
||
| Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); | ||
| cipher.init(Cipher.ENCRYPT_MODE, | ||
| new SecretKeySpec(keyBytes, "AES"), | ||
| new IvParameterSpec(ivBytes)); | ||
| return Base64.encodeBase64String(cipher.doFinal(data.getBytes(UTF_8))); | ||
| } catch (Exception e) { | ||
| throw new WxRuntimeException("AES加密失败!", e); | ||
| } |
| // 模拟来自 getUserEncryptKey 接口返回的 encrypt_key(Base64)和 iv(Hex,32位即16字节) | ||
| private static final String ENCRYPT_KEY = "VI6BpyrK9XH4i4AIGe86tg=="; | ||
| private static final String HEX_IV = "6003f73ec441c3866003f73ec441c386"; | ||
|
|
| @Test | ||
| public void testEncryptAndDecryptWithEncryptKey() { | ||
| String plainText = "{\"userId\":\"12345\",\"amount\":100}"; | ||
|
|
||
| String encrypted = WxMaCryptUtils.encryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, plainText); | ||
| assertThat(encrypted).isNotNull().isNotEmpty(); | ||
|
|
||
| String decrypted = WxMaCryptUtils.decryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, encrypted); | ||
| assertThat(decrypted).isEqualTo(plainText); | ||
| } | ||
|
|
||
| /** | ||
| * 测试加密网络通道的加解密对称性(不同明文). | ||
| */ | ||
| @Test | ||
| public void testEncryptDecryptSymmetryWithEncryptKey() { | ||
| String plainText = "hello miniprogram"; | ||
|
|
||
| String encrypted = WxMaCryptUtils.encryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, plainText); | ||
| String decrypted = WxMaCryptUtils.decryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, encrypted); | ||
| assertThat(decrypted).isEqualTo(plainText); | ||
| } |
| private static byte[] hexToBytes(String hex) { | ||
| if (hex == null || hex.length() % 2 != 0) { | ||
| throw new IllegalArgumentException("无效的十六进制字符串格式:长度必须为偶数"); | ||
| } | ||
| int len = hex.length(); | ||
| byte[] data = new byte[len / 2]; | ||
| for (int i = 0; i < len; i += 2) { | ||
| int high = Character.digit(hex.charAt(i), 16); | ||
| int low = Character.digit(hex.charAt(i + 1), 16); | ||
| if (high == -1 || low == -1) { | ||
| throw new IllegalArgumentException("无效的十六进制字符串格式:包含非法字符 '" + hex.charAt(high == -1 ? i : i + 1) + "'"); | ||
| } | ||
| data[i / 2] = (byte) ((high << 4) + low); | ||
| } |
微信小程序加密网络通道(
getUserEncryptKey)API 已有基础实现,但存在签名计算错误、错误码判断缺陷,且缺少配套的 AES 加解密工具方法。Bug 修复
WxMaInternetServiceImplsha256()原先用sessionKey.getBytes(UTF_8)(base64 字符串的 UTF-8 字节)作为 HMAC key,应改为Base64.decodeBase64(sessionKey)(原始密钥字节),与微信其他加密 API 惯例一致errcode == -1仅捕获单一错误码且有 NPE 风险,改为errcode != null && errcode != 0新增功能
WxMaCryptUtils新增针对加密网络通道的 AES-128-CBC 工具方法,支持getUserEncryptKey返回的 Base64 编码 key 和 Hex 编码 iv:hexToBytes()内部辅助方法含输入校验(奇数长度、非法字符),非法输入抛出IllegalArgumentException。